﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using YJKGridTopologyCal.Algorithm;
using YJKGridTopologyCal.Geometries;

namespace YJKGridTopologyCal.Operation
{
	public class BoundaryOp
	{
		public static IGeometry GetBoundary(IGeometry g)
		{
			return new BoundaryOp(g).GetBoundary();
		}

		public static IGeometry GetBoundary(IGeometry g, IBoundaryNodeRule bnRule)
		{
			return new BoundaryOp(g, bnRule).GetBoundary();
		}

		public BoundaryOp(IGeometry geom) : this(geom, BoundaryNodeRules.Mod2BoundaryRule)
		{
		}

		public BoundaryOp(IGeometry geom, IBoundaryNodeRule bnRule)
		{
			this._geom = geom;
			this._geomFact = geom.Factory;
			this._bnRule = bnRule;
		}

		public IGeometry GetBoundary()
		{
			if (this._geom is ILineString)
			{
				return this.BoundaryLineString((ILineString)this._geom);
			}
			if (this._geom is IMultiLineString)
			{
				return this.BoundaryMultiLineString((IMultiLineString)this._geom);
			}
			return this._geom.Boundary;
		}

		private IMultiPoint GetEmptyMultiPoint()
		{
            //return this._geomFact.CreateMultiPoint(null);
            return null;
		}

		private IGeometry BoundaryMultiLineString(IMultiLineString mLine)
		{
			if (this._geom.IsEmpty)
			{
				return this.GetEmptyMultiPoint();
			}
			Coordinate[] array = this.ComputeBoundaryCoordinates(mLine);
			if (array.Length == 1)
			{
				return this._geomFact.CreatePoint(array[0]);
			}
			return this._geomFact.CreateMultiPoint(array);
		}

		private Coordinate[] ComputeBoundaryCoordinates(IMultiLineString mLine)
		{
			IList<Coordinate> list = new List<Coordinate>();
			this._endpointMap = new SortedDictionary<Coordinate, Counter>();
			for (int i = 0; i < mLine.NumGeometries; i++)
			{
				ILineString lineString = (ILineString)mLine.GetGeometryN(i);
				if (lineString.NumPoints != 0)
				{
					this.AddEndpoint(lineString.GetCoordinateN(0));
					this.AddEndpoint(lineString.GetCoordinateN(lineString.NumPoints - 1));
				}
			}
			foreach (KeyValuePair<Coordinate, Counter> keyValuePair in this._endpointMap)
			{
				int count = keyValuePair.Value.Count;
				if (this._bnRule.IsInBoundary(count))
				{
					list.Add(keyValuePair.Key);
				}
			}
			return CoordinateArrays.ToCoordinateArray(list);
		}

		private void AddEndpoint(Coordinate pt)
		{
			Counter counter;
			if (!this._endpointMap.TryGetValue(pt, out counter))
			{
				counter = new Counter();
				this._endpointMap.Add(pt, counter);
			}
			counter.Count++;
		}

		private IGeometry BoundaryLineString(ILineString line)
		{
			if (this._geom.IsEmpty)
			{
				return this.GetEmptyMultiPoint();
			}
			if (!line.IsClosed)
			{
				return this._geomFact.CreateMultiPoint(new IPoint[]
				{
					line.StartPoint,
					line.EndPoint
				});
			}
			if (this._bnRule.IsInBoundary(2))
			{
				return line.StartPoint;
			}
			return null;
		}

		private readonly IGeometry _geom;

		private readonly IGeometryFactory _geomFact;

		private readonly IBoundaryNodeRule _bnRule;

		private IDictionary<Coordinate, Counter> _endpointMap;
	}
}
